#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

#include"./includes/LeerYGrabarSector.h"
#include"./includes/variablesGlobales.h"
#include"./includes/FuncionesArchConfig.h"
#include"./includes/conexiones.h"
#include "../../Commons/src/includes/nipc.h"

#define BACKLOG 100000

int main(int argc, char **argv) {

	sector_t contenidoSector;
	char nombreDisco[50];
	paqueteNIPC *paqueteP;
	paqueteNIPC paquete;
	int32_t socketPFS[10];
	uint32_t r;
	char sector[512];
	payloadEscritura_t payloadEscritura;
	t_operaciones *operaciones; /*despues hay que hacer un free de operaciones*/
	char *pathDisco;
	datosPDD_t datos_PDD;
	uint32_t sectoresPorDisco = 1048576;

	fd_set master; // conjunto maestro de descriptores de fichero
	fd_set read_fds; // conjunto temporal de descriptores de fichero para select();
	struct sockaddr_in remoteaddr; // dirección del cliente
	int fdmax; // número máximo de descriptores de fichero
	int newfd; // descriptor de socket de nueva conexión aceptada
	char buf[528]; // buffer para datos del cliente
	int nbytes;
	int yes=1; // para setsockopt() SO_REUSEADDR, más abajo
	int addrlen;
	int i;
	uint32_t cant_PFS = 0;
	uint32_t socketPFSAUX;
	uint32_t cantMaxPFS;
	uint32_t n;

	cargarArchivoDeConfiguracion();

	inicializarPFS(socketPFS,10);

	paqueteP=malloc(sizeof(paqueteNIPC));

	if(argc != 2){
		puts("Falta el parámetro del path");
		pathDisco = "/home/utn_so/Desarrollo/fat32.disk";
//		return -1;
	}else{

	pathDisco = argv[1];};

	strcpy(nombreDisco,pathDisco);

	/* Creo el Socket: SOCK_STREAM para TCP y SOCK_DGRAM par UDP */
	int descriptor = socket(AF_INET, SOCK_STREAM, 0);

	/* Direccion Local */
	struct sockaddr_in *local_address = malloc(sizeof(struct sockaddr_in));
	/* Direccion remota ( a la que me quiero conectar )-->RAID */
	struct sockaddr_in *remote_address = malloc(sizeof(struct sockaddr_in));

	{       /* Con esto fuerzo a que el puerto local sea el 5201 y que tome la IP por defecto de la PC */
		local_address->sin_family = AF_INET;
		local_address->sin_addr.s_addr = INADDR_ANY;
		local_address->sin_port = htons(PuertoPDD);
		memset(&(local_address->sin_zero), '\0', 8);



		bind(descriptor, (struct sockaddr *)local_address, sizeof(struct sockaddr_in));
	}

	{       /* Con esto indico que me quiero conectar al puerto 9048 de la IP 127.0.0.1 (localhost) -> RAID*/
		remote_address->sin_family = AF_INET;
		remote_address->sin_addr.s_addr = inet_addr(direccionIPRaid);

		remote_address->sin_port = htons(PuertoDelRaid);

	}

	operaciones = operaciones_inicializar(pathDisco);

	/* Me conecto al servidor */
	if(connect(descriptor, (struct sockaddr *)remote_address , sizeof(struct sockaddr_in))==-1){
		perror("connect");
		puts("No se pudo conectar al RAID");

/* ------------------------------------------Si se conecta al PFS------------------------------------------------------ */

		FD_ZERO(&master); // borra los conjuntos maestro y temporal
		FD_ZERO(&read_fds); // obtener socket a la escucha


		// obviar el mensaje "address already in use" (la dirección ya se está usando)
		if (setsockopt(descriptor, SOL_SOCKET, SO_REUSEADDR, &yes,
				sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}


		// escuchar
		if (listen(descriptor, 10) == -1) {
			perror("listen");
			exit(1);
		}

		// añadir descriptor al conjunto maestro
		FD_SET(descriptor, &master);

		// seguir la pista del descriptor de fichero mayor
		fdmax = descriptor; // por ahora es éste

		// bucle principal
		for(;;) {
			paqueteP = malloc(sizeof(paqueteNIPC));
			read_fds = master; // cópialo
			if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
				perror("select");
				exit(1);
			}

			// explorar conexiones existentes en busca de datos que leer
			for(i = 0; i <= fdmax; i++) {
				if (FD_ISSET(i, &read_fds)) { // ¡¡tenemos datos!!

					if (i == descriptor) {
						// gestionar nuevas conexiones
						addrlen = sizeof(remoteaddr);
						if ((newfd = accept(descriptor, (struct sockaddr*)&remoteaddr,&addrlen)) == -1)
						{
							perror("accept");
						} else {

							//Handshake
							if(recv(newfd, &paquete, sizeof(paquete), MSG_WAITALL)==-1){
								perror("recv");
							}else{
								memcpy(&cantMaxPFS,paquete.payload,sizeof(uint32_t));
								if(paquete.num_msg==0){
									if(paquete.longitud == 0){ //es PFS
										if(cant_PFS<cantMaxPFS){
											for(n=0;n<cantMaxPFS;n++){
												if(socketPFS[n]==-1){
													socketPFS[n]=newfd;
													n=cantMaxPFS;
													cant_PFS++;
												}
											}


											printf("El socket del PFS es %d\n",i);
											paqueteP->num_msg = 0;
											paqueteP->longitud = 0;



											if(send(newfd,paqueteP,sizeof(paqueteNIPC),0)==-1){
												perror("send");
												puts("Error de respuesta al PFS en el HS\n");
											} else {
												puts("HS con PFS exitoso\n");
											};
										} else {
											paqueteP->num_msg=-1;
											strcpy(paqueteP->payload,"Se llegó al máximo de conexiones de PFS");
											paqueteP->longitud=sizeof("Se llegó al máximo de conexiones de PFS");
											if(send(newfd,paqueteP,sizeof(paqueteNIPC),0)==-1){
												perror("send");};
											printf("Se llegó al máximo de conexiones de PFS");};

									};

									FD_SET(newfd, &master); // añadir al conjunto maestro

									if (newfd > fdmax) {
										// actualizar el máximo
										fdmax = newfd;
									}
									printf("selectserver: new connection from %s on "
											"socket %d\n", inet_ntoa(remoteaddr.sin_addr),
											newfd);

								} else {puts("No es handshake");}
							}
						}
					} else {
						// gestionar datos de un cliente
						if ((nbytes = recv(i, buf, sizeof(buf), MSG_WAITALL)) <= 0) {
							// error o conexión cerrada por el cliente
							if (nbytes == 0) {
								// conexión cerrada
								printf("selectserver: socket %d hung up\n", i);
								if(esPFS(i,socketPFS,cantMaxPFS)){
									for(n=0;n<cantMaxPFS;n++)
									{
										if(socketPFS[n]==i){
											socketPFS[n]=-1;
										};
									};
									cant_PFS--;
								} else {

								};
							} else {
								perror("recv");
							}

							close(i); // ¡Hasta luego!


							FD_CLR(i, &master); // eliminar del conjunto maestro

						} else {

							/*Nos habla un PFS o PDD*/
							if(esPFS(i,socketPFS,cantMaxPFS)){
								memcpy(&paquete, buf, sizeof(paqueteNIPC));
								switch(paquete.num_msg){
								case 1:
									// Pedido de lectura (PFS)
									//Busca sector
									//EL SOCKET ES i, metelo donde quieras

													memcpy(&payloadEscritura,paquete.payload,sizeof(paquete.payload));

													contenidoSector = leerSector2(payloadEscritura.sector,operaciones);

													memcpy(sector,contenidoSector.datos,512);
													paqueteP->num_msg = 2;
													paqueteP->longitud = sizeof(uint32_t) + sizeof(sector);

													memcpy(&payloadEscritura.sector,paquete.payload, sizeof(uint32_t));
													memcpy(&payloadEscritura.contenido,sector, sizeof(sector));
													memcpy(&paqueteP->payload,&payloadEscritura,516);

										if(send(i, paqueteP, sizeof(paqueteNIPC), 0)==-1){
												perror("send");
												puts("Falló el envío del paquete al RAID");
											} else {
									//			puts("Se envió del PDD al RAID");
											};
									break;

								case 3:
									//Peticion de escritura (PFS)
									//Busca Sector y escribirlo
									//EL SOCKET ES i, metelo donde quieras.
										memcpy(&payloadEscritura, paquete.payload, 516);

										memcpy(&contenidoSector.idSector,&payloadEscritura.sector,sizeof(uint32_t));
										memcpy(&contenidoSector.datos,&payloadEscritura.contenido,sizeof(payloadEscritura.contenido));
										grabarSector(operaciones,payloadEscritura.sector, &contenidoSector);

//
//										paqueteP->num_msg = 4;
//										paqueteP->longitud = sizeof(uint32_t);
//
//										memset(payloadEscritura.contenido,'\0',516);
//
//										memcpy(&paqueteP->payload,&payloadEscritura,sizeof(payloadEscritura));
//
//										if(send(i, paqueteP, sizeof(paqueteNIPC), 0)==-1){
//												perror("send");
//												puts("Falló el envío del paquete al RAID");
//											} else {
//												puts("Se envió del PDD al RAID");
//											};

									break;

								default:
									break;
								}
							}



						}
					} // Esto es ¡TAN FEO!
				}
			}
		free(paqueteP);
		}

			return 0;





		free(operaciones);

		/* ------------------------------------------Termina conexión PFS------------------------------------------------------ */

	}else{

		/* ------------------------------------------Si se conecta al RAID------------------------------------------------------ */



	paqueteP->num_msg = 0;

	strcpy(datos_PDD.nome,nombreDisco);
	datos_PDD.sectoresPorDisco = sectoresPorDisco;
	memcpy(&paqueteP->payload,&datos_PDD,sizeof(datos_PDD));

	paqueteP->longitud = sizeof(nombreDisco);

	if(send(descriptor, paqueteP, sizeof(paqueteNIPC), 0)==-1){
		perror("send");
		puts("Falló el envío del paquete al RAID para HS");
	};

	if(recv(descriptor, &paquete, sizeof(paqueteNIPC), MSG_WAITALL)==-1){
		perror("recv");
		puts("No se pudo recibir la respuesta del intento de Handshake con RAID");
	};

	if((paquete.num_msg==0)&&(paquete.longitud==0)){
		puts("Handshake con RAID exitoso");
	operaciones = operaciones_inicializar(pathDisco);
	} else {
		puts("El Handshake con RAID falló");
	};


	while(1){

	if(recv(descriptor, &paquete, sizeof(paqueteNIPC), MSG_WAITALL)==-1){
		perror("recv");
	    puts("No se recibió nada");
	};

	paqueteP = malloc(sizeof(paqueteNIPC));


	if(paquete.num_msg==1){
		//Petición de lectura (RAID)

				memcpy(&payloadEscritura,paquete.payload,sizeof(paquete.payload));

				socketPFSAUX=payloadEscritura.socket;

				contenidoSector = leerSector2(payloadEscritura.sector,operaciones);

				memcpy(sector,contenidoSector.datos,512);
				paqueteP->num_msg = 2;
				paqueteP->longitud = sizeof(uint32_t) + sizeof(sector) + sizeof(payloadEscritura.socket);

				memcpy(&payloadEscritura.sector,paquete.payload, sizeof(uint32_t));
				memcpy(&payloadEscritura.contenido,sector, sizeof(sector));
				payloadEscritura.socket=socketPFSAUX;

				memcpy(&paqueteP->payload,&payloadEscritura,520);

	if(send(descriptor, paqueteP, sizeof(paqueteNIPC), 0)==-1){
			perror("send");
			puts("Falló el envío del paquete al RAID");
		};


	};

	if(paquete.num_msg==3){
	//Petición de escritura (RAID)
	memcpy(&payloadEscritura, paquete.payload, 520);
	socketPFSAUX=payloadEscritura.socket;

	memcpy(&contenidoSector.idSector,&payloadEscritura.sector,sizeof(uint32_t));
	memcpy(&contenidoSector.datos,&payloadEscritura.contenido,sizeof(payloadEscritura.contenido));
	grabarSector(operaciones,payloadEscritura.sector, &contenidoSector);


	paqueteP->num_msg = 4;
	paqueteP->longitud = sizeof(uint32_t);

	//El sector ya está en payloadEscritura.sector
	memset(payloadEscritura.contenido,'\0',512);
	payloadEscritura.socket=socketPFSAUX;

	memcpy(&paqueteP->payload,&payloadEscritura,sizeof(payloadEscritura));

	if(send(descriptor, paqueteP, sizeof(paqueteNIPC), 0)==-1){
			perror("send");
			puts("Falló el envío del paquete al RAID");
		} else {
			puts("Se envió del PDD al RAID");
		};


	};

	if(paquete.num_msg==5){
		//Petición de lectura por sincronización (RAID)

				memcpy(&payloadEscritura,paquete.payload,sizeof(paquete.payload));

				contenidoSector = leerSector2(payloadEscritura.sector,operaciones);

				memcpy(sector,contenidoSector.datos,512);
				paqueteP->num_msg = 5;
				paqueteP->longitud = sizeof(uint32_t) + sizeof(sector);

				memcpy(&payloadEscritura.sector,paquete.payload, sizeof(uint32_t));
				memcpy(&payloadEscritura.contenido,sector, sizeof(sector));
				memcpy(&paqueteP->payload,&payloadEscritura,516);

	if(send(descriptor, paqueteP, sizeof(paqueteNIPC), 0)==-1){
			perror("send");
			puts("Falló el envío del paquete al RAID");
		};


	};

	if(paquete.num_msg==6){
	//Petición de escritura por sincronización (RAID)

	memcpy(&payloadEscritura, paquete.payload, 516);
	contenidoSector.idSector = payloadEscritura.sector;
	memcpy(&contenidoSector.datos, &payloadEscritura.contenido, sizeof(payloadEscritura.contenido));

	grabarSector(operaciones,payloadEscritura.sector, &contenidoSector);

	};

		free(paqueteP);

	};
	free(operaciones);
};

	/* ------------------------------------------Termina conexión con el RAID------------------------------------------------------ */

	/* Cierro el socket y por ende la conexion */
	close(descriptor);

	free(local_address);
	free(remote_address);

	return EXIT_SUCCESS;
}
